/**
 *
 * $URL: https://192.168.0.12:8443/svn/dentist/trunk/gyomu/ndsscommon/src/jp/co/nttdsanyo/ndsscommon/web/struts/validator/CustomFieldChecks.java $
 * $Rev: 18131 $
 * $Author: hcf $
 * $Date: 2009-11-09 11:17:11 +0900 (月, 09 11 2009) $
 *
 * Copyright(C) 2009 by NTTDATA SANYO SYSTEM. All Rights Reserved.
 */
package web.struts.validator;


import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.Validator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.validator.FieldChecks;
import org.apache.struts.validator.Resources;


/**
 * @author NDSS fushimi
 * 
 */
public class CustomFieldChecks extends FieldChecks
{
    private static final long serialVersionUID = 1033442981535297127L;
    /**
     * ログクラス。
     */
    //[ソースコード診断 目視-35]start
    //Loggerはprivate static final で宣言
    //private static Log log = LogFactory.getLog( CustomFieldChecks.class );
    private static final Log log = LogFactory.getLog( CustomFieldChecks.class );
    //[ソースコード診断 目視-35]end
    



    /**
     * <p>
     * 指定された値のチェックディジットをモジュラス10で判定する
     * </p>
     * ウェイトは"weight"パラメータで指定します。（指定の無い場合はデフォルトで"2"が使用されます）
     * 
     * @param obj
     *            検査対象のオブジェクト
     * @param va
     *            <code>Validator</code>により用意された <code>ValidatorAction</code>
     * @param field
     *            フィールドオブジェクト
     * @param errors
     *            ActionMessages アクションエラー
     * @param validator
     *            Validatorインスタンス
     * @param request
     *            HTTPリクエスト
     * @return 入力値が正しければ <code>true</code>
     */
    public static boolean validateModulus10( Object obj, ValidatorAction va, Field field, ActionMessages errors,
        Validator validator, HttpServletRequest request )
    {

        // beanがnullの時、エラーログを出力し、trueを返却する。
        if ( obj == null )
        {
            return true;
        }

        // 検証対象をStringで取得
        String value = null;
        if ( isString( obj ) )
        {
            value = ( String ) obj;
        }
        else
        {
            value = ValidatorUtils.getValueAsString( obj, field.getProperty() );
        }

        if ( log.isTraceEnabled() )
        {
            log.trace( "チェックディジット妥当性チェック[対象：" + value + "]" );
        }

        // 検証値がnullまたは空文字の時、true返却
        if ( GenericValidator.isBlankOrNull( value ) )
        {
            return true;
        }

        // チェック除外パターンを取得
        String ignorePattern = field.getVarValue( "ignorePattern" );

        if ( StringUtils.isNotBlank( ignorePattern ) )
        {
            // 除外パターンと一致した場合は検証せずにtrueで抜ける
            if ( value.matches( ignorePattern ) )
            {
                if ( log.isTraceEnabled() )
                {
                    log.trace( "除外パターンマッチ[" + ignorePattern + "]" );
                }
                return true;
            }
        }

        // ウェイトを取得（デフォルト値は2"）
        int weight = NumberUtils.toInt( field.getVarValue( "weight" ), 2 );

        // チェックディジットの妥当性チェック
        if ( isValidModulus10( value, weight ) )
        {
            return true;
        }
        else
        {
            // エラーだった場合はメッセージを埋める
            errors.add( field.getKey(), Resources.getActionMessage( validator, request, va, field ) );
            return false;
        }
    }



    /**
     * <p>
     * 指定された日付の逆転チェックを行う
     * </p>
     * チェック対象はyyyyMMddの西暦文字列とする
     * 
     * @param obj
     *            検査対象のオブジェクト
     * @param va
     *            <code>Validator</code>により用意された <code>ValidatorAction</code>
     * @param field
     *            フィールドオブジェクト
     * @param errors
     *            ActionMessages アクションエラー
     * @param validator
     *            Validatorインスタンス
     * @param request
     *            HTTPリクエスト
     * @return 入力値が正しければ <code>true</code>
     */
    public static boolean validateDateOrder( Object obj, ValidatorAction va, Field field, ActionMessages errors,
        Validator validator, HttpServletRequest request )
    {

        // beanがnullの時、エラーログを出力し、trueを返却する。
        if ( obj == null )
        {
            return true;
        }

        // 検証対象の西暦をStringで取得
        String seirekiBefore = null;
        if ( isString( obj ) )
        {
            seirekiBefore = ( String ) obj;
        }
        else
        {
            seirekiBefore = ValidatorUtils.getValueAsString( obj, field.getProperty() );
        }

        // 検証値がnullまたは空文字の時、true返却
        if ( GenericValidator.isBlankOrNull( seirekiBefore ) )
        {
            return true;
        }

        // 比較対象の日付を取得
        Object form = validator.getParameterValue( Validator.BEAN_PARAM );
        String seirekiAfter = ValidatorUtils.getValueAsString( form, field.getVarValue( "afterDate" ) );

        // 比較対象がnullまたは空文字の時、true返却
        if ( GenericValidator.isBlankOrNull( seirekiAfter ) )
        {
            return true;
        }

        // 和暦カレンダーを生成してみて、エラーが発生したら妥当でないと判断する
        try
        {

            SimpleDateFormat fmt = new SimpleDateFormat( "yyyyMMdd" );
            Date dateBefore = fmt.parse( seirekiBefore );
            Date dateAfter = fmt.parse( seirekiAfter );

            // 検証対象が比較対象より未来日だった場合はエラー
            if ( dateBefore.after( dateAfter ) )
            {
                errors.add( field.getKey(), Resources.getActionMessage( validator, request, va, field ) );
                return false;
            }

        }
        catch ( Exception e )
        {
            // 日付で無かった場合はtrueにする
            // 日付書式は正しい前提
            return true;
        }

        return true;
    }


    /**
     * <p>
     * 指定された和暦文字列の妥当性をチェックする。
     * </p>
     * 対象は和暦文字列とし、元号コードのプロパティ名は、"gengoCodeProperty"パラメータで指定します。
     * 
     * @param obj
     *            検査対象のオブジェクト
     * @param va
     *            <code>Validator</code>により用意された <code>ValidatorAction</code>
     * @param field
     *            フィールドオブジェクト
     * @param errors
     *            ActionMessages アクションエラー
     * @param validator
     *            Validatorインスタンス
     * @param request
     *            HTTPリクエスト
     * @return 入力値が正しければ <code>true</code>
     */
    public static boolean validateSignedNumber( Object obj, ValidatorAction va, Field field, ActionMessages errors,
        Validator validator, HttpServletRequest request )
    {

        // beanがnullの場合はtrueを返却する。
        if ( obj == null )
        {
            return true;
        }

        // 検証対象をStringで取得
        String value = null;
        if ( isString( obj ) )
        {
            value = ( String ) obj;
        }
        else
        {
            value = ValidatorUtils.getValueAsString( obj, field.getProperty() );
        }

        // 検証値がnullまたは空文字の時、true返却
        if ( GenericValidator.isBlankOrNull( value ) )
        {
            return true;
        }

        // 符号の有無を取得
        boolean signed = BooleanUtils.toBoolean( field.getVarValue( "signed" ) );

        // 整数部の桁数を取得
        String IntegerLength = field.getVarValue( "IntegerLength" );

        // 小数部の桁数を取得
        String scale = field.getVarValue( "scale" );

        // 判定用の正規表現を生成する
        //[ソース診断対応:目視チェック start]
        //StringBufferよりStringBuilderを利用
        //StringBuffer sb = new StringBuffer();
        StringBuilder sb = new StringBuilder();
        //[ソース診断対応:目視チェック end]

        sb.append( "^" );
        // 符号の有無
        if ( signed )
        {
            sb.append( "-?" );
        }
        // 整数部
        sb.append( "[0-9]{1," ).append( IntegerLength ).append( "}" );
        // 小数部
        if ( NumberUtils.toInt( scale, 0 ) > 0 )
        {
            sb.append( "(\\.[0-9]{1," ).append( scale ).append( "})?" );
        }
        sb.append( "$" );

        if ( log.isTraceEnabled() )
        {
            log.trace( "正規表現判定判定[regex:" + sb.toString() + " value: " + value + "]" );
        }

        // 入力値が正規表現とマッチするかをチェックする
        Pattern pattern = Pattern.compile( sb.toString() );
        Matcher matcher = pattern.matcher( value );

        if ( !matcher.matches() )
        {
            // エラーだった場合はメッセージを埋める
            errors.add( field.getKey(), Resources.getActionMessage( validator, request, va, field ) );
            return false;
        }
        return true;
    }


    /**
     * <p>
     * 指定された数値の範囲を検証する
     * </p>
     * 範囲指定は符号、マイナス値も指定可能です
     * 
     * @param obj
     *            検査対象のオブジェクト
     * @param va
     *            <code>Validator</code>により用意された <code>ValidatorAction</code>
     * @param field
     *            フィールドオブジェクト
     * @param errors
     *            ActionMessages アクションエラー
     * @param validator
     *            Validatorインスタンス
     * @param request
     *            HTTPリクエスト
     * @return 入力値が正しければ <code>true</code>
     */
    public static boolean validateSignedNumberRange( Object bean, ValidatorAction va, Field field,
        ActionMessages errors, Validator validator, HttpServletRequest request )
    {

        String value = null;
        if ( isString( bean ) )
        {
            value = ( String ) bean;
        }
        else
        {
            value = ValidatorUtils.getValueAsString( bean, field.getProperty() );
        }

        //入力あり、かつ入力値が数値文字列だった場合のみチェックを行う
        if ( !GenericValidator.isBlankOrNull( value ) && NumberUtils.isNumber( value ) )
        {
            double doubleValue = NumberUtils.toDouble( value );
            double min = NumberUtils.toDouble( field.getVarValue( "min" ) );
            double max = NumberUtils.toDouble( field.getVarValue( "max" ) );

            if ( !GenericValidator.isInRange( doubleValue, min, max ) )
            {
                errors.add( field.getKey(), Resources.getActionMessage( validator, request, va, field ) );
                return false;
            }
        }

        return true;
    }


    /**
     * 指定された文字列のチェックディジットが正しいかを判定する。
     * 
     * @param code
     *            検証対象文字列
     * @param weight
     *            ウェイト
     * @return チェックディジットが正しいか否か
     */
    public static boolean isValidModulus10( String code, int weight )
    {

        // 分解用のテンポラリ
        int tmp = Integer.parseInt( code );

        // チェックディジットを取り出す
        int digit = tmp % 10;
        // ディジットを除いた番号を取得
        tmp = tmp / 10;

        if ( log.isTraceEnabled() )
        {
            log.trace( "チェックディジット判定[コード：" + tmp + " Digit：" + digit + "]" );
        }

        // ディジット以外の桁のウェイト込みの合計を計算
        int sum = 0;
        int codeLength = Integer.toString( tmp ).length();
        for ( int i = 1; i <= codeLength; i++ )
        {
            if ( ( i % 2 ) == 1 )
            {
                // 故障対応:UTG0T_0014 start
                // 掛け算した結果が2桁になった場合、1桁目 と 2桁目 を分割してから、合計する。
                int tmp1 = ( tmp % 10 ) * weight;
                if ( tmp1 >= 10 )
                {
                    sum = sum + tmp1 / 10 + tmp1 % 10;
                }
                else
                {
                    sum = sum + ( tmp % 10 ) * weight;
                }
                // 故障対応:UTG0T_0014 END
            }
            else
            {
                sum = sum + ( tmp % 10 ) * 1;
            }
            tmp = tmp / 10;
        }

        int calcDigit = ( sum % 10 ) == 0 ? 0 : 10 - ( sum % 10 );

        if ( log.isTraceEnabled() )
        {
            log.trace( "算出結果[トータル：" + sum + " Digit：" + calcDigit + "]" );
        }

        return calcDigit == digit;

    }





    /**
     * <p>
     * 時間の時と時間の分の関連チェック
     * </p>
     * 時間の時と時間の分いずれかが入力されている場合、すべて入力が必要です
     * 
     * @param obj
     *            検査対象のオブジェクト
     * @param va
     *            <code>Validator</code>により用意された <code>ValidatorAction</code>
     * @param field
     *            フィールドオブジェクト
     * @param errors
     *            ActionMessages アクションエラー
     * @param validator
     *            Validatorインスタンス
     * @param request
     *            HTTPリクエスト
     * @return 入力値が正しければ <code>true</code>
     */
    public static boolean validateHourAndMinute( Object obj, ValidatorAction va, Field field, ActionMessages errors,
        Validator validator, HttpServletRequest request )
    {

        // beanがnullの時、trueを返却する。
        if ( obj == null )
        {
            return true;
        }

        //検証対象のプロパティの名前を取得
        String propName1 = field.getProperty();
        String propName2 = field.getVarValue( "otherProperty" );
        // 検証対象のプロパティの値を取得
        String value1 = null;
        String value2 = null;

        if ( isString( obj ) )
        {
            return true;
        }
        else
        {
            value1 = ValidatorUtils.getValueAsString( obj, propName1 );
            value2 = ValidatorUtils.getValueAsString( obj, propName2 );
        }

        // 検証値がnullまたは空文字の時、true返却
        if ( ( GenericValidator.isBlankOrNull( value1 ) && !GenericValidator.isBlankOrNull( value2 ) )
            || ( !GenericValidator.isBlankOrNull( value1 ) && GenericValidator.isBlankOrNull( value2 ) ) )
        {
            //エラーメセッジを添加
            errors.add( field.getKey(), Resources.getActionMessage( validator, request, va, field ) );
            //ログ出力
            log.trace( propName1 + "、" + propName2 + "のいずれかを入力した場合、" + propName1 + "、" + propName2 + "を全て入力して下さい。" );
            return false;
        }

        return true;
    }


    /**
     * <p>
     * なしとありのチェックボックスが同時選択するチェック
     * </p>
     * なしとありのチェックボックスが不能同時選択する
     * 
     * @param obj
     *            検査対象のオブジェクト
     * @param va
     *            <code>Validator</code>により用意された <code>ValidatorAction</code>
     * @param field
     *            フィールドオブジェクト
     * @param errors
     *            ActionMessages アクションエラー
     * @param validator
     *            Validatorインスタンス
     * @param request
     *            HTTPリクエスト
     * @return 入力値が正しければ <code>true</code>
     */
    public static boolean validateMultiChecked( Object obj, ValidatorAction va, Field field, ActionMessages errors,
        Validator validator, HttpServletRequest request )
    {

        // beanがnullの時、trueを返却する。
        if ( obj == null )
        {
            return true;
        }

        // 検証対象のプロパティの値を取得
        String[] value = null;

        if ( isString( obj ) )
        {
            return true;
        }
        else
        {
            try
            {
                value = ( String[] ) PropertyUtils.getProperty( obj, field.getProperty() );
            }
            catch ( Exception ignore )
            {
                //[ソース診断対応:目視チェック start]
                //Exception.printStackTrace() を利用しない
                log.error( "プロパティコピーエラー", ignore );
                //[ソース診断対応:目視チェック end]
            }
            //            catch ( IllegalAccessException e )
            //            {
            //                e.printStackTrace();
            //            }
            //            catch ( InvocationTargetException e )
            //            {
            //                e.printStackTrace();
            //            }
            //            catch ( NoSuchMethodException e )
            //            {
            //                e.printStackTrace();
            //            }
        }

        // 検証値が長度チェック
        if ( value.length > 1 )
        {
            //エラーメセッジを添加
            errors.add( field.getKey(), Resources.getActionMessage( validator, request, va, field ) );
            //ログ出力
            log.trace( field.getProperty() + "は、なりとありのいずれか片方のみ選択してください" );
            return false;
        }

        return true;
    }
}
